local loadScan = true
local showFriendly = true

dofile(SCRIPT_PATH .. "libs/3DTo2D.lua")

WardList = {
    ward = {
        new = function(self, type, x, y, z, spawnTime, object, drawingOptions, index, drawingSet)
            local wards = {
                SightWard = { name = "SightWard", color = { 0, 1, 0.4, 1 }, range = 1450, durationTime = 180, overlayMark = "w", inGameMarkType = "wards", circleRadius = 100, },
                WriggleLantern = { name = "WriggleLantern", color = { 0, 1, 0.4, 1 }, range = 1450, durationTime = 180, overlayMark = "w", inGameMarkType = "wards", circleRadius = 100, },
                VisionWard = { name = "VisionWard", color = { 1, 0.3, 1, 1 }, range = 1450, durationTime = 180, overlayMark = "w", inGameMarkType = "wards", circleRadius = 100, },
                TentativeVisionWard = { name = "VisionWard", color = { 1, 0.3, 1, 1 }, range = 1450, durationTime = 180, overlayMark = ".", inGameMarkType = "wards", circleRadius = 100, },
                TentativeSightWard = { name = "SightWard", color = { 0, 1, 0.4, 1 }, range = 1450, durationTime = 180, overlayMark = ".", inGameMarkType = "wards", circleRadius = 100, },
                JackInTheBox = { name = "ShacoBox", color = { 1, 0.3, 0.3, 1 }, range = 780, durationTime = 60, overlayMark = "o", inGameMarkType = "boxes", circleRadius = 90, },
                MaokaiSapling2 = { name = "MaokaiSproutling", color = { 1, 0.3, 0.3, 1 }, range = 780, durationTime = 35, overlayMark = "o", inGameMarkType = "boxes", circleRadius = 90, },
                BantamTrap = { name = "TeemoMushroom", color = { 1, 0.3, 0.3, 1 }, range = 425, durationTime = 600, overlayMark = "o", inGameMarkType = "boxes", circleRadius = 90, },
                CaitlynYordleTrap = { name = "CaitlynTrap", color = { 0.9, 0.9, 1, 1 }, durationTime = 240, overlayMark = "o", inGameMarkType = "traps", circleRadius = 60, },
                Bushwhack = { name = "Nidalee_Spear", color = { 0.9, 0.9, 1, 1 }, durationTime = 240, overlayMark = "o", inGameMarkType = "traps", circleRadius = 60, },
            }
            local content
            local type = type
            if wards[type] then content = wards[type] else
                for k, v in pairs(wards) do
                    if v.name == type then
                        content = wards[k]
                        type = k
                    end
                end
            end
            if not content then return end
            content.type, content.x, content.y, content.z, content.spawnTime, content.object, content.drawingOptions, content.index, content.drawingSet = type, x, y, z, spawnTime, object, drawingOptions, index, drawingSet

            if index and drawingSet then
                local mapx, mapz = convertToMinimap(x, z)
                if not drawingOptions or not string.find(drawingOptions, "NoCircle") then
                    drawingSet:Add("circle_" .. content.inGameMarkType .. content.index, DrawingCircle(content.circleRadius, { x = x, y = y, z = z }))
                end
            end

            setmetatable(content, { __index = self, __eq = self.equal })
            return content
        end,
        equal = function(self, other)
            return self.type == other.type and
                    (((not self.object or not other.object) and self:distance(other) <= 100) or
                            (self.object and other.object and self.object == other.object))
        end,
        remove = function(self)
            if self.drawingSet then
                self.drawingSet:Remove("circle_" .. self.inGameMarkType .. self.index)
                self.drawingSet:Remove("text_" .. self.inGameMarkType .. self.index)
                self.drawingSet:Remove("visioncircle_" .. self.inGameMarkType .. self.index)
            end
            self = nil
        end,
        distance = function(self, other)
            return math.sqrt((self.x - other.x) * (self.x - other.x) + (self.z - other.z) * (self.z - other.z))
        end,
        timeout = function(self)
            local remainingTime = self.durationTime * 1000 + self.spawnTime - os.clock() * 1000
            if remainingTime < 10000 then
                if self.drawingSet["circle_" .. self.inGameMarkType .. self.index] then
                    self.drawingSet:Get("circle_" .. self.inGameMarkType .. self.index):RunAnimation("radiusPulse", PULSE, RADIUS, 1, 0, 30, 1, true)
                end
            end
            if (not self.object or (self.object.valid and not self.object.dead)) and remainingTime > 0 then return remainingTime
            else self:remove()
            end
            return 0
        end,
    },
    new = function(self)
        local content = { memory = {}, ds = DrawingSets(), lastDrawn = 0 }
        setmetatable(content, { __index = self })
        return content
    end,
    draw = function(self)
        de:Draw(self.ds)
        if self.lastDrawn ~= os.clock() then
            self.lastDrawn = os.clock()
            de:ComputeAnimations(self.ds)
        end
    end,
    delete = function(self, object)
        for i, v in ipairs(self.memory) do
            if not object or v.object == object then self.memory[i]:remove() end
        end
        if not object then self = nil end
    end,
    insert = function(self, object)
        for i = 1, self:getObjectCount() + 1, 1 do
            if self:getObject(i) == nil then
                self.memory[i] = self.ward:new(object.type, object.x, object.y, object.z, object.spawnTime, object.object, object.drawingOptions, i, self.ds)
                return i
            end
        end
    end,
    add = function(self, name, x, y, z, spawnTime, object, drawingOptions) --second three are optional
        local cWard = self.ward:new(name, x, y, z, spawnTime or os.clock() * 1000, object, drawingOptions)
        if cWard then
            for i, v in ipairs(self.memory) do --overwrites if same
                if v == cWard then --equal check -> metatable
                    if not name == "TentativeVisionWard" and not name == "TentativeSightWard" and
                            spawnTime > self.memory[i].spawnTime then cWard.spawnTime = self.memory[i].spawnTime
                    end
                    if self.memory[i].object then cWard.object = self.memory[i].object end
                    self.memory[i]:remove()
                    self.memory[i] = self.ward:new(name, x, y, z, spawnTime or os.clock() * 1000, object, drawingOptions, i, self.ds)
                    return i
                end
            end
            self:insert(cWard)
        end
    end,
    addTentative = function(self, type, x, z, x2, z2, time, movementSpeed)
    --x, z are the position where enemy has been gone
    --time is when the enemy has been gone
        local pos
        if type == "VisionWard" or type == "SightWard" then
            --list with common places for Wards
            pos = { { 2530, -74, 10975 }, { 3928, 39, 11614 }, { 8963, 46, 11296 }, { 1847, 44, 9727 }, { 4267, -71, 9531 }, { 6300, 46, 9490 }, { 2696, 46, 7452 }, { 9575, 46, 7170 }, { 9875, -70, 5086 }, { 6669, 47, 2938 }, { 8648, 50, 1930 }, }
        else return
        end
        for i, v in ipairs(pos) do
            local t1, t2 = math.sqrt((v[1] - x) * (v[1] - x) + (v[3] - z) * (v[3] - z)) / (movementSpeed * 0.8), math.sqrt((v[1] - x2) * (v[1] - x2) + (v[3] - z2) * (v[3] - z2)) / (movementSpeed * 0.8)
            if t1 + t2 < os.clock * 1000 - time then self:add("Tentative" .. type, v[1], v[2], v[3], os.clock * 1000 - t1, nil, "NoIndex") end
        end
    end,
    getObject = function(self, index)
        if self.memory[index] and self.memory[index]:timeout() > 0 then return self.memory[index] end
    end,
    getObjectCount = function(self)
        return #self.memory
    end,
}

EnemyWards, FriendlyWards = WardList:new(), showFriendly and WardList:new()

local function checkWard(object, team)
    return object and object.type == "obj_AI_Minion" and object.team == (team and team or TEAM_ENEMY)
end

function Spell(object, spellName, spellLevel, x1, y1, z1, x2, y2, z2)
    if object and object.type == "obj_AI_Hero" and object.team == TEAM_ENEMY then
        EnemyWards:add(spellName, x2, y2, z2, os.clock() * 1000, nil, "NoIndex")
    end
end

function Status(object, status, newValue)
    if checkWard(object) and status == STATUS_MANA then
        EnemyWards:add(object.charName, object.x, object.y, object.z, os.clock() * 1000 - (object.maxMana - newValue) * 1000, object, "NoIndex")
    end
end

function Create(object)
    if checkWard(object) then
        EnemyWards:add(object.charName, object.x, object.y, object.z, os.clock() * 1000 - (object.maxMana - object.mana), object, "NoIndex")
    end
    if showFriendly and checkWard(object, player.team) then
        FriendlyWards:add(object.charName, object.x, object.y, object.z, os.clock() * 1000 - (object.maxMana - object.mana), object, "NoMinimap" .. "NoCircle")
    end
end

function Delete(object)
    if checkWard(object) then
        EnemyWards:delete(object)
    end
    if checkWard(object, player.team) then
        if showFriendly then FriendlyWards:delete(object) end
    end
end

local tracking = {}
function Drawer()
    local function draw3DTimer(wardList, timer, text)
        for i = 1, wardList:getObjectCount(), 1 do
            local object = wardList:getObject(i)
            if object then --draw timer and name over position
                local x, y, onScreen = get2DFrom3D(object.x, object.y + 200, object.z + 50)
                if onScreen then
                    local t = math.floor(object:timeout() / 1000 % 3600 / 60) .. " : " .. (object:timeout() / 1000 % 60 > 10 and math.floor(object:timeout() / 1000 % 60) or ("0" .. math.floor(object:timeout() / 1000 % 60)))
                    if text then DrawText(object.name .. " N°" .. object.index, 14, x, y - 11, object.color[1], object.color[2], object.color[3], object.color[4], FONT_CENTER) end
                    if timer then DrawText(t, 14, x, y, object.color[1], object.color[2], object.color[3], object.color[4], FONT_CENTER) end
                end
            end
        end
    end

    if showFriendly then draw3DTimer(FriendlyWards, true, false) end
    draw3DTimer(EnemyWards, true, true)
    EnemyWards:draw()
    if FriendlyWards then FriendlyWards:draw() end
    if not lastTracked or lastTracked - os.clock() > 0.1 then
        lastTracked = os.clock()
        local function getInvCount(object, id)
            local ItemSlot = { ITEM_SLOT_1, ITEM_SLOT_2, ITEM_SLOT_3, ITEM_SLOT_4, ITEM_SLOT_5, ITEM_SLOT_6, }
            local count = 0
            for i = 1, 6, 1 do
                local item, stack = object:GetInventorySlot(ItemSlot[i])
                if item == id then if stack then count = count + stack else count = count + 1 end end
            end
            return count
        end

        for k, hero in pairs(tracking) do
            if hero.object.visible then
                if hero.wasInvisible then
                    tracking[k].wasInvisible = false
                    if getInvCount(hero.object, 2043) < (tracking[k].VisionWard or 0) then
                        PrintChat("Tentative VisionWards Added")
                        EnemyWards:addTentative("VisionWard", hero.object.x, hero.object.z, hero.lastPos.x, hero.lastPos.z, hero.lastSeen, hero.object.ms)
                    end
                    if getInvCount(hero.object, 2044) < (tracking[k].SightWard or 0) then
                        PrintChat("Tentative SightWards Added")
                        EnemyWards:addTentative("SightWard", hero.object.x, hero.object.z, hero.lastPos.x, hero.lastPos.z, hero.lastSeen, hero.object.ms)
                    end
                end
                tracking[k].VisionWard = getInvCount(hero.object, 2043)
                tracking[k].SightWard = getInvCount(hero.object, 2044)
                if tracking[k].VisionWard > 0 or tracking[k].VisionWard > 0 then
                    tracking[k].lastPos = { hero.object.x, hero.object.z }
                    tracking[k].lastSeen = os.clock() * 1000
                end
            else tracking[k].wasInvisible = true
            end
        end
    end
end

function Load()
    for i = 1, GetHeroCount(), 1 do
        local object = GetHero(i)
        if object.team == TEAM_ENEMY then tracking.charName = { object = object } end
    end
    if loadScan then
        for i = 1, GetObjectCount(), 1 do
            local object = GetObject(i)
            if checkWard(object) then EnemyWards:add(object.charName, object.x, object.y, object.z, os.clock() * 1000 - (object.maxMana - object.mana) * 1000, object, "NoIndex") end
            if checkWard(object, player.team) then FriendlyWards:add(object.charName, object.x, object.y, object.z, os.clock() * 1000 - (object.maxMana - object.mana), object, "NoMinimap" .. "NoCircle") end
        end
    end
    script.deleteObjectCallback = "Delete"
    script.processSpellCallback = "Spell"
    script.createObjectCallback = "Create"
    script.statusCallback = "Status"
    script.drawCallback = "Drawer"
end

